package com.azarias.dfs.namenode.server.component;


import java.io.IOException;

/**
 * @description: 负责管理edits log日志的核心组件
 * @author: Azarias_Jx
 * @date: 2021/12/31 16:15
 */

/**
 * 		为什么不直接落盘?
 * 		    是因为io太慢，对于并发来说效率太低
 * 		为什么用双缓冲机制?
 *
 */

public class FSEditlog {

    /**
     * 当前递增到的txid值
     */
    private long txidSeq = 0L;

    /**
     * 双缓冲区
     */
    private DoubleBuffer editLogBuffer = new DoubleBuffer();
    /**
     * 每个线程自己本地的txid副本
     */
    private ThreadLocal<Long> localTxid = new ThreadLocal<Long>();
    /**
     * 需要刷入磁盘
     */
    private volatile boolean shouldSyncToDisk = false;

    /**
     * 正在写入磁盘
     */
    private volatile boolean isSyncRunning = false;

    /**
     * 写入磁盘的最大txid
     */
    private volatile long syncTxid = 0L;

    /**
     * 创建日志
     * @param content
     */
    public void logEdit(String content) throws IOException {
        synchronized (this) {
            //将日志写入buffer的时候有可能正在进行数据同步这边需要等待一下
            waitSchedulingSync();
            //只有抢到锁的才能进来，这个时候txid一定是顺序自增的
            txidSeq++;
            long txid = txidSeq;
            localTxid.set(txid);

            EditLog editLog = new EditLog(txid, content);

            try {
                editLogBuffer.write(editLog);
            } catch (IOException e) {
                e.printStackTrace();

            }
            //判断是否buffer写满了
            //如果写满了的话需要交换buffer
            if (!editLogBuffer.shouldSyncToDisk()) {
                return;
            }
            shouldSyncToDisk = true;
        }
        //为什么这边没有同步在上一个锁中
        //因为这边是刷盘操作，io操作消耗比较大时间比较久，所以这边如果也加进sy锁的话，效率太低了
        logSync();
    }

    /**
     * 等待正在调度的刷磁盘的操作
     */
    private void waitSchedulingSync() {
        //为什么这边在刷盘的时候不允许继续进入??
        // TODO
        try {
            while (shouldSyncToDisk) {
                wait(1000);
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 将内存缓冲中的数据刷入磁盘文件中
     * 在这里尝试允许某一个线程一次性将内存缓冲中的数据刷入磁盘文件中
     * 相当于实现一个批量将内存缓冲数据刷磁盘的过程
     */
    private void logSync() throws IOException {
        //这边用synchronized加锁，因为锁的对象也是this，所以上面的logEdit方法里也会阻塞住
        synchronized (this) {
            Long txid = localTxid.get();
            if (isSyncRunning) {

//                if (txid < syncTxid) {
//                    return;
//                }

                try {
                    while (isSyncRunning) {
                        wait(1000);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            //交换editlog
            editLogBuffer.setCurrentToSync();
            // 然后可以保存一下当前要同步到磁盘中去的最大的txid
            // 此时editLogBuffer中的syncBuffer这块区域，交换完以后这里可能有多条数据
            // 而且他里面的edits log的txid一定是从小到大的
            // 此时要同步的txid = 6,7,8,9,10,11,12
            syncTxid = txid;
            shouldSyncToDisk = false;
            notifyAll(); // 唤醒那些还卡在while循环那儿的线程
            isSyncRunning = true;
        }

        //开始刷入磁盘
        editLogBuffer.flush();


        synchronized (this) {
            // 同步完了磁盘之后，就会将标志位复位，再释放锁
            isSyncRunning = false;
            // 唤醒可能正在等待他同步完磁盘的线程
            notifyAll();
        }
    }

    /**
     * 强制把内存缓冲里的数据刷入磁盘中
     */
    public void flush() {
        try {
            editLogBuffer.setCurrentToSync();
            editLogBuffer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
